<!--
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
  "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>WebGL TDL Example</title>
<style>
html, body {
  width: 100%;
  height: 100%;
  border: 0px;
  padding: 0px;
  margin: 0px;
  background-color: red;
  font-family: sans-serif;
  overflow: hidden;
  color: #fff;
}
a {
  color: #fff;
}
#info {
    font-size: small;
    position: absolute;
	top: 0px; width: 100%;
	padding: 5px;
	text-align: center;
	z-index: 2;
}
CANVAS {
  background-color: gray;
}
.fpsContainer {
  position: absolute;
  top: 10px;
  left: 10px;
  z-index: 2;
  color: white;
  font-family: sans-serif;
  background-color: rgba(0,0,0,0.5);
  border-radius: 10px;
  padding: 10px;
}
#viewContainer {
  width: 100%;
  height: 100%;
}
</style>
<script type="text/javascript" src="../tdl/base.js"></script>
<script type="text/javascript">
tdl.require('tdl.buffers');
tdl.require('tdl.fast');
tdl.require('tdl.fps');
tdl.require('tdl.framebuffers');
tdl.require('tdl.log');
tdl.require('tdl.math');
tdl.require('tdl.models');
tdl.require('tdl.primitives');
tdl.require('tdl.programs');
tdl.require('tdl.textures');
tdl.require('tdl.webgl');
window.onload = initialize;

// globals
var gl;                   // the gl context.
var canvas;               // the canvas
var math;                 // the math lib.
var fast;                 // the fast math lib.
var g_fpsTimer;           // object to measure frames per second;
var g_logGLCalls = true;  // whether or not to log webgl calls
var g_debug = false;      // whether or not to debug.
var g_drawOnce = false;   // draw just one frame.

//g_drawOnce = true;
//g_debug = true;

var g_eyeSpeed          = 0.2;
var g_eyeHeight         = 2;
var g_eyeRadius         = 9;

function ValidateNoneOfTheArgsAreUndefined(functionName, args) {
  for (var ii = 0; ii < args.length; ++ii) {
    if (args[ii] === undefined) {
      tdl.error("undefined passed to gl." + functionName + "(" +
                tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
    }
  }
}

function Log(msg) {
  if (g_logGLCalls) {
    tdl.log(msg);
  }
}

function LogGLCall(functionName, args) {
  if (g_logGLCalls) {
    ValidateNoneOfTheArgsAreUndefined(functionName, args)
    tdl.log("gl." + functionName + "(" +
                tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
  }
}

function createProgramFromTags(vertexTagId, fragmentTagId) {
  return tdl.programs.loadProgram(
      document.getElementById(vertexTagId).text,
      document.getElementById(fragmentTagId).text);
}

/**
 * Sets up Planet.
 */
function setupSphere() {
  var textures = {
    diffuseSampler: tdl.textures.loadTexture('assets/sometexture.png')};
  var program = createProgramFromTags(
      'sphereVertexShader',
      'sphereFragmentShader');
  var arrays = tdl.primitives.createSphere(0.4, 10, 12);

  return new tdl.models.Model(program, arrays, textures);
}

function initialize() {
  math = tdl.math;
  fast = tdl.fast;
  canvas = document.getElementById("canvas");
  g_fpsTimer = new tdl.fps.FPSTimer();

  gl = tdl.webgl.setupWebGL(canvas);
  if (!gl) {
    return false;
  }
  if (g_debug) {
    gl = tdl.webgl.makeDebugContext(gl, undefined, LogGLCall);
  }

  Log("--Setup Sphere---------------------------------------");
  var sphere = setupSphere();

  var pickingProgram = createProgramFromTags(
      'pickingVertexShader',
      'pickingFragmentShader');

  var pickingFramebuffer = new tdl.framebuffers.Framebuffer(gl.canvas.width, gl.canvas.height, true);
  var backbuffer = tdl.framebuffers.getBackBuffer(gl.canvas);

  var then = 0.0;
  var clock = 0.0;
  var fpsElem = document.getElementById("fps");
  var pickElem = document.getElementById("pick");
  var pickIndex = -1;

  // pre-allocate a bunch of arrays
  var projection = new Float32Array(16);
  var view = new Float32Array(16);
  var world = new Float32Array(16);
  var worldInverse = new Float32Array(16);
  var worldInverseTranspose = new Float32Array(16);
  var viewProjection = new Float32Array(16);
  var worldViewProjection = new Float32Array(16);
  var viewInverse = new Float32Array(16);
  var viewProjectionInverse = new Float32Array(16);
  var eyePosition = new Float32Array(3);
  var target = new Float32Array(3);
  var up = new Float32Array([0,1,0]);
  var lightWorldPos = new Float32Array(3);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = new Float32Array(16);
  var m4t1 = new Float32Array(16);
  var m4t2 = new Float32Array(16);
  var m4t3 = new Float32Array(16);
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);

  // Sphere uniforms.
  var sphereConst = {
    viewInverse: viewInverse,
    lightWorldPos: lightWorldPos,
    specular: one4,
    shininess: 50,
    specularFactor: 0.2};
  var spherePer = {
    lightColor: new Float32Array([0,0,0,1]),
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};
  var pickingPer = {
    u_color: new Float32Array(4),
    worldViewProjection: worldViewProjection
  };

  var frameCount = 0;
  function render() {
    ++frameCount;
    if (!g_drawOnce) {
      tdl.webgl.requestAnimationFrame(render, canvas);
    }
    var now = (new Date()).getTime() * 0.001;
    var elapsedTime;
    if(then == 0.0) {
      elapsedTime = 0.0;
    } else {
      elapsedTime = now - then;
    }
    then = now;

    g_fpsTimer.update(elapsedTime);
    fpsElem.innerHTML = g_fpsTimer.averageFPS;

    clock += elapsedTime;
    eyePosition[0] = Math.sin(clock * g_eyeSpeed) * g_eyeRadius;
    eyePosition[1] = g_eyeHeight;
    eyePosition[2] = Math.cos(clock * g_eyeSpeed) * g_eyeRadius;

    gl.colorMask(true, true, true, true);
    gl.depthMask(true);
    gl.clearColor(0,0,0,0);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);

    drawScene(false);

    // Set the alpha to 255.
    gl.colorMask(false, false, false, true);
    gl.clearColor(0,0,0,1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // turn off logging after 1 frame.
    g_logGLCalls = false;
  }

  canvas.addEventListener('click', pick, false);

  var pickColor = new Uint8Array(4);
  function pick(event) {
    var info = getEventInfo(event);
    var m = getRelativeCoordinates(info);
    // convert mouse coords to WebGL coords
    var pickX = m.x * pickingFramebuffer.width / gl.canvas.clientWidth;
    var pickY = pickingFramebuffer.height - (m.y * pickingFramebuffer.height / gl.canvas.clientHeight) - 1;

    // Bind the picking framebuffer to draw offscreen
    pickingFramebuffer.bind();

    // clear to 1,1,1,1 (outside the range of objects)
    gl.colorMask(true, true, true, true);
    gl.depthMask(true);
    gl.clearColor(1,1,1,1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    gl.enable(gl.CULL_FACE);
    gl.enable(gl.DEPTH_TEST);
    gl.disable(gl.BLEND);

    // Swap in the picking program
    sphere.oldProgram = sphere.program;
    sphere.program = pickingProgram;

    // Draw the scene to the framebuffer
    drawScene(true);

    // Read the pixel under the mouse
    gl.readPixels(pickX, pickY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pickColor);

    // Compute which model by drawing order was picked.
    pickIndex = pickColor[0] + pickColor[1] * 0x100 + pickColor[2] * 0x10000;
    pickElem.innerHTML = pickIndex;

    // Restore the old program
    sphere.program = sphere.oldProgram;

    // Restore the backbuffer so future drawing is visible again
    backbuffer.bind();
  }

  function drawScene(picking) {
    fast.matrix4.perspective(
        projection,
        math.degToRad(60),
        canvas.clientWidth / canvas.clientHeight,
        1,
        5000);
    fast.matrix4.lookAt(
        view,
        eyePosition,
        target,
        up);
    fast.matrix4.mul(viewProjection, view, projection);
    fast.matrix4.inverse(viewInverse, view);
    fast.matrix4.inverse(viewProjectionInverse, viewProjection);

    fast.matrix4.getAxis(v3t0, viewInverse, 0); // x
    fast.matrix4.getAxis(v3t1, viewInverse, 1); // y;
    fast.matrix4.getAxis(v3t2, viewInverse, 2); // z;
    fast.mulScalarVector(v3t0, 10, v3t0);
    fast.mulScalarVector(v3t1, 10, v3t1);
    fast.mulScalarVector(v3t2, 10, v3t2);
    fast.addVector(lightWorldPos, eyePosition, v3t0);
    fast.addVector(lightWorldPos, lightWorldPos, v3t1);
    fast.addVector(lightWorldPos, lightWorldPos, v3t2);

  //      view: view,
  //      projection: projection,
  //      viewProjection: viewProjection,

    Log("--Draw sphere---------------------------------------");
    sphere.drawPrep(sphereConst);
    var modelCount = 0;
    var across = 6;
    var lightColor = spherePer.lightColor;
    var half = (across - 1) * 0.5;
    for (var xx = 0; xx < across; ++xx) {
      for (var yy = 0; yy < across; ++yy) {
        for (var zz = 0; zz < across; ++zz) {
          lightColor[0] = xx / across;
          lightColor[1] = yy / across;
          lightColor[2] = zz / across;
          var scale = (xx + yy + zz) % 4 / 4 + 0.5;
          fast.matrix4.scaling(m4t0, [scale, scale, scale]);
          fast.matrix4.translation(m4t1, [xx - half, yy - half, zz - half]);
          fast.matrix4.mul(world, m4t0, m4t1);
          fast.matrix4.mul(worldViewProjection, world, viewProjection);
          if (picking) {
            // convert model count to a color.
            pickingPer.u_color[0] = ((modelCount >>  0) & 0xFF) / 255;
            pickingPer.u_color[1] = ((modelCount >>  8) & 0xFF) / 255;
            pickingPer.u_color[2] = ((modelCount >> 16) & 0xFF) / 255;
            pickingPer.u_color[3] = ((modelCount >> 24) & 0xFF) / 255;
            sphere.draw(pickingPer);
          } else {
            // highlight picked object.
            if (pickIndex == modelCount && (Math.floor(clock * 4) & 1)) {
              lightColor[0] = 255;
              lightColor[1] = 255;
              lightColor[2] = 255;
            }
            fast.matrix4.inverse(worldInverse, world);
            fast.matrix4.transpose(worldInverseTranspose, worldInverse);
            sphere.draw(spherePer);
          }
          ++modelCount;
        }
      }
    }
  }

  render();
  return true;
}

/**
 * Returns the absolute position of an element for certain browsers.
 * @param {HTML Element} element The element to get a position for.
 * @return {Object} An object containing x and y as the absolute position
 *     of the given element.
 */
var getAbsolutePosition = function(element) {
  var r = { x: element.offsetLeft, y: element.offsetTop };
  if (element.offsetParent) {
    var tmp = getAbsolutePosition(element.offsetParent);
    r.x += tmp.x;
    r.y += tmp.y;
  }
  return r;
};

 /**
  * Retrieve the coordinates of the given event relative to the center
  * of the widget.
  *
  * @param {eventInfo} eventInfo As returned from
  *     CLIENT3DJS.util.getEventInfo.
  * @param {HTML Element} opt_reference A DOM element whose position we want
  *     to transform the mouse coordinates to. If it is not passed in the
  *     element in the eventInfo will be used.
  * @return {Object} An object containing keys 'x' and 'y'.
  */
var getRelativeCoordinates = function(eventInfo, opt_reference) {
  var x, y;
  var event = eventInfo.event;
  var element = eventInfo.element;
  var reference = opt_reference || eventInfo.element;
  if (!window.opera && typeof event.offsetX != 'undefined') {
    // Use offset coordinates and find common offsetParent
    var pos = { x: event.offsetX, y: event.offsetY };
    // Send the coordinates upwards through the offsetParent chain.
    var e = element;
    while (e) {
      e.mouseX = pos.x;
      e.mouseY = pos.y;
      pos.x += e.offsetLeft;
      pos.y += e.offsetTop;
      e = e.offsetParent;
    }
    // Look for the coordinates starting from the reference element.
    var e = reference;
    var offset = { x: 0, y: 0 }
    while (e) {
      if (typeof e.mouseX != 'undefined') {
        x = e.mouseX - offset.x;
        y = e.mouseY - offset.y;
        break;
      }
      offset.x += e.offsetLeft;
      offset.y += e.offsetTop;
      e = e.offsetParent;
    }
    // Reset stored coordinates
    e = element;
    while (e) {
      e.mouseX = undefined;
      e.mouseY = undefined;
      e = e.offsetParent;
    }
  } else {
    // Use absolute coordinates
    var pos = getAbsolutePosition(reference);
    x = event.pageX - pos.x;
    y = event.pageY - pos.y;
  }
  // Subtract distance to middle
  return { x: x, y: y };
};

var getEventInfo = function(event) {
  event = event ? event : window.event;
  var element = event.target ? event.target : event.srcElement;
  return {
    event: event,
    element: element,
    name: (element.id ? element.id : ('->' + element.toString())),
    wheel: (event.detail ? event.detail : -event.wheelDelta),
    shift: (event.modifiers ? (event.modifiers & Event.SHIFT_MASK) : event.shiftKey)
  };
};

</script>
</head>
<body>
<div id="info"><a href="http://threedlibrary.googlecode.com" target="_blank">tdl.js</a> - example</div>
<div class="fpsContainer">
  <div class="fps">fps: <span id="fps"></div>
  <div>You picked: <span id="pick"></span></div>
</div>
<div id="viewContainer">
<canvas id="canvas" width="1024" height="1024" style="width: 100%; height: 100%;"></canvas>
</div>
</body>
<script id="sphereVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform vec3 lightWorldPos;
uniform mat4 world;
uniform mat4 viewInverse;
uniform mat4 worldInverseTranspose;
attribute vec4 position;
attribute vec3 normal;
attribute vec2 texCoord;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;
void main() {
  v_texCoord = texCoord;
  v_position = (worldViewProjection * position);
  v_normal = (worldInverseTranspose * vec4(normal, 0)).xyz;
  v_surfaceToLight = lightWorldPos - (world * position).xyz;
  v_surfaceToView = (viewInverse[3] - (world * position)).xyz;
  gl_Position = v_position;
}

</script>
<script id="sphereFragmentShader" type="text/something-not-javascript">
#ifdef GL_ES
precision highp float;
#endif
uniform vec4 lightColor;
varying vec4 v_position;
varying vec2 v_texCoord;
varying vec3 v_normal;
varying vec3 v_surfaceToLight;
varying vec3 v_surfaceToView;

uniform sampler2D diffuseSampler;
uniform vec4 specular;
uniform sampler2D bumpSampler;
uniform float shininess;
uniform float specularFactor;

vec4 lit(float l ,float h, float m) {
  return vec4(1.0,
              max(l, 0.0),
              (l > 0.0) ? pow(max(0.0, h), m) : 0.0,
              1.0);
}
void main() {
  vec4 diffuse = texture2D(diffuseSampler, v_texCoord);
  vec3 normal = normalize(v_normal);
  vec3 surfaceToLight = normalize(v_surfaceToLight);
  vec3 surfaceToView = normalize(v_surfaceToView);
  vec3 halfVector = normalize(surfaceToLight + surfaceToView);
  vec4 litR = lit(dot(normal, surfaceToLight),
                    dot(normal, halfVector), shininess);
  gl_FragColor = vec4((
  lightColor * (diffuse * litR.y
                        + specular * litR.z * specularFactor)).rgb,
      diffuse.a);
}
</script>
<script id="pickingVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
attribute vec4 position;
void main() {
  gl_Position = worldViewProjection * position;
}

</script>
<script id="pickingFragmentShader" type="text/something-not-javascript">
precision mediump float;

uniform vec4 u_color;

void main() {
  gl_FragColor = u_color;
}
</script>
</html>


